// --------------------------------------------------------------------------------------------------------------------
// <copyright file="SqlServerHelper.cs" company="SmartPlan">
//   SmartPlan 2011
// </copyright>
// <summary>
//   Defines the SqlServerHelper type.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Sql;
using System.Runtime.InteropServices;
using System.Text;

namespace DbDiff.WinClient.Helper
{
    /// <summary>
    /// The sql server helper.
    /// </summary>
    public static class SqlServerHelper
    {
        /// <summary>
        /// The sq l_ handl e_ env.
        /// </summary>
        private const short SQL_HANDLE_ENV = 1;

        /// <summary>
        /// The sq l_ handl e_ dbc.
        /// </summary>
        private const short SQL_HANDLE_DBC = 2;

        /// <summary>
        /// The sq l_ att r_ odb c_ version.
        /// </summary>
        private const int SQL_ATTR_ODBC_VERSION = 200;

        /// <summary>
        /// The sq l_ o v_ odb c 3.
        /// </summary>
        private const int SQL_OV_ODBC3 = 3;

        /// <summary>
        /// The sq l_ success.
        /// </summary>
        private const short SQL_SUCCESS = 0;

        /// <summary>
        /// The sq l_ nee d_ data.
        /// </summary>
        private const short SQL_NEED_DATA = 99;

        /// <summary>
        /// The defaul t_ resul t_ size.
        /// </summary>
        private const short DEFAULT_RESULT_SIZE = 1024;

        /// <summary>
        /// The sq l_ drive r_ str.
        /// </summary>
        private const string SQL_DRIVER_STR = "DRIVER=SQL SERVER";

        /// <summary>
        /// The sql alloc handle.
        /// </summary>
        /// <param name="hType">
        /// The h type.
        /// </param>
        /// <param name="inputHandle">
        /// The input handle.
        /// </param>
        /// <param name="outputHandle">
        /// The output handle.
        /// </param>
        /// <returns>
        /// The sql alloc handle.
        /// </returns>
        [DllImport("odbc32.dll")]
        private static extern short SQLAllocHandle(short hType, IntPtr inputHandle, out IntPtr outputHandle);

        /// <summary>
        /// The sql set env attr.
        /// </summary>
        /// <param name="henv">
        /// The henv.
        /// </param>
        /// <param name="attribute">
        /// The attribute.
        /// </param>
        /// <param name="valuePtr">
        /// The value ptr.
        /// </param>
        /// <param name="strLength">
        /// The str length.
        /// </param>
        /// <returns>
        /// The sql set env attr.
        /// </returns>
        [DllImport("odbc32.dll")]
        private static extern short SQLSetEnvAttr(IntPtr henv, int attribute, IntPtr valuePtr, int strLength);

        /// <summary>
        /// The sql free handle.
        /// </summary>
        /// <param name="hType">
        /// The h type.
        /// </param>
        /// <param name="handle">
        /// The handle.
        /// </param>
        /// <returns>
        /// The sql free handle.
        /// </returns>
        [DllImport("odbc32.dll")]
        private static extern short SQLFreeHandle(short hType, IntPtr handle);

        /// <summary>
        /// The sql browse connect.
        /// </summary>
        /// <param name="hconn">
        /// The hconn.
        /// </param>
        /// <param name="inString">
        /// The in string.
        /// </param>
        /// <param name="inStringLength">
        /// The in string length.
        /// </param>
        /// <param name="outString">
        /// The out string.
        /// </param>
        /// <param name="outStringLength">
        /// The out string length.
        /// </param>
        /// <param name="outLengthNeeded">
        /// The out length needed.
        /// </param>
        /// <returns>
        /// The sql browse connect.
        /// </returns>
        [DllImport("odbc32.dll", CharSet = CharSet.Ansi)]
        private static extern short SQLBrowseConnect(IntPtr hconn, StringBuilder inString, 
                                                     short inStringLength, StringBuilder outString, 
                                                     short outStringLength, 
                                                     out short outLengthNeeded);

        /// <summary>
        /// The get servers odbc.
        /// </summary>
        /// <returns>
        /// </returns>
        /// <exception cref="ApplicationException">
        /// </exception>
        /// <exception cref="DbDiffClientException">
        /// </exception>
        private static List<string> GetServersODBC()
        {
            string[] retval = null;
            string servers = string.Empty;
            IntPtr henv = IntPtr.Zero;
            IntPtr hconn = IntPtr.Zero;
            var driverIn = new StringBuilder(SQL_DRIVER_STR);
            var driverOut = new StringBuilder(DEFAULT_RESULT_SIZE);
            short length = (short)driverIn.Length;

            try
            {
                if (SQLAllocHandle(SQL_HANDLE_ENV, henv, out henv) == SQL_SUCCESS)
                {
                    if (SQL_SUCCESS == SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (IntPtr)SQL_OV_ODBC3, 0))
                    {
                        if (SQL_SUCCESS == SQLAllocHandle(SQL_HANDLE_DBC, henv, out hconn))
                        {
                            short lenNeeded = 0;
                            if (SQL_NEED_DATA ==
                                SQLBrowseConnect(hconn, driverIn, length, driverOut, DEFAULT_RESULT_SIZE, out lenNeeded))
                            {
                                if (DEFAULT_RESULT_SIZE < lenNeeded)
                                {
                                    driverOut.Capacity = lenNeeded;
                                    if (SQL_NEED_DATA !=
                                        SQLBrowseConnect(hconn, driverIn, length, driverOut, lenNeeded, out lenNeeded))
                                    {
                                        throw new ApplicationException("Unabled to aquire SQL Servers from ODBC driver.");
                                    }
                                }

                                servers = driverOut.ToString();
                                int start = servers.IndexOf("{") + 1;
                                int len = servers.IndexOf("}") - start;
                                if ((start > 0) && (len > 2))
                                {
                                    servers = servers.Substring(start, len);
                                }
                                else
                                {
                                    servers = string.Empty;
                                }
                            }
                        }
                    }
                }

                if (servers.Length > 0)
                {
                    retval = servers.Split(',');
                }
            }
            catch (Exception e)
            {
                throw new DbDiffClientException("ERROR: Get sql servers", e);
            }
            finally
            {
                if (hconn != IntPtr.Zero)
                {
                    SQLFreeHandle(SQL_HANDLE_DBC, hconn);
                }

                if (henv != IntPtr.Zero)
                {
                    SQLFreeHandle(SQL_HANDLE_ENV, hconn);
                }
            }

            var serverList = new List<string>();
            return serverList;
        }

        /// <summary>
        /// </summary>
        /// <param name="dotnetMode">
        /// The dotnet mode.
        /// </param>
        /// <returns>
        /// </returns>
        public static List<string> GetServers(bool dotnetMode)
        {
            return dotnetMode ? GetServersNET() : GetServersODBC();
        }

        /// <summary>
        /// The get servers.
        /// </summary>
        /// <returns>
        /// The sql servers in netvork
        /// </returns>
        private static List<string> GetServersNET()
        {
            SqlDataSourceEnumerator sql = SqlDataSourceEnumerator.Instance;

            DataTable tbl = sql.GetDataSources();

            var serverList = new List<string>();

            foreach (DataRow row in tbl.Rows)
            {
                string str = (string)row["ServerName"];
                if (row["InstanceName"] != DBNull.Value)
                {
                    str += string.Format("\\{0}", row["InstanceName"]);
                }

                serverList.Add(str);
            }

            return serverList;
        }
    }
}